/*
 * Copyright (C) The Apache Software Foundation. All rights reserved.
 *
 * This software is published under the terms of the Apache Software License
 * version 1.1, a copy of which has been included with this distribution in
 * the LICENSE file.
 */
package ranab.server.ftp;

import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.InetAddress;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

import ranab.util.Message;
import ranab.io.IoUtils;
import ranab.io.StreamConnectorObserver;

/**
 * This is a generic ftp connection handler. It delegates 
 * the request to appropriate methods in subclasses.
 *
 * @author <a href="mailto:rana_b@yahoo.com">Rana Bhattacharyya</a>
 */
public
class BaseFtpConnection implements Runnable, StreamConnectorObserver {
    
    protected final static Class[] METHOD_INPUT_SIG = new Class[] {FtpRequest.class, FtpWriter.class};
    
    protected FtpConfig mConfig                 = null;
    protected FtpStatus mFtpStatus              = null;
    protected FtpDataConnection mDataConnection = null;
    protected FtpUser mUser                     = null;
    protected SpyConnectionInterface mSpy       = null;
    protected FtpConnectionObserver mObserver   = null;
    protected Socket mControlSocket             = null;
    protected FtpWriter mWriter                 = null;
    protected boolean mbStopRequest             = false;


    /**
     * Set configuration file and the control socket.
     */
    public BaseFtpConnection(FtpConfig ftpConfig, Socket soc) {
        mConfig = ftpConfig;
        mFtpStatus = mConfig.getStatus();
        mControlSocket = soc;
        mUser = new FtpUser();
    }

    /**
     * Server one FTP connection.
     */
    public void run() {
        InetAddress clientAddress = mControlSocket.getInetAddress();
        mConfig.getLogger().info("Handling new request from " + clientAddress.getHostAddress());
        mDataConnection = new FtpDataConnection(mConfig);
        mUser.setClientAddress(clientAddress);
        mConfig.getConnectionService().newConnection(this);
        
        BufferedReader in = null;
        try {
            in = new BufferedReader(new InputStreamReader(mControlSocket.getInputStream(), "ASCII")); 
            mWriter = new FtpWriter(mControlSocket, mConfig);                    
            
            // permission check
            if( !mConfig.getIpRestrictor().hasPermission(mControlSocket.getInetAddress()) ) {
                mWriter.write(mFtpStatus.getResponse(530, null, mUser, null));
                return;
            }
            mWriter.write(mFtpStatus.getResponse(220, null, mUser, null));
            
            do {
                notifyObserver();
                String commandLine = in.readLine();
                
                // test command line
                if(commandLine == null) {
                    break;
                }
                
                spyRequest(commandLine);
                if(commandLine.equals("")) {
                    continue;
                }
                
                FtpRequest request = new FtpRequest(commandLine);
                if(!hasPermission(request)) {
                    mWriter.write(mFtpStatus.getResponse(530, request, mUser, null));
                    break;
                }
                    
                // execute command
                service(request, mWriter);
            }
            while(!mbStopRequest);
        }
        catch(Exception ex) {
        }
        finally {
            IoUtils.close(in);
            IoUtils.close(mWriter);
            ConnectionService conService = mConfig.getConnectionService();
            if (conService != null) {
                conService.closeConnection(mUser.getSessionId());
            }
        }
    }
    
    
    /**
     * Execute the ftp command.
     */
    public void service(FtpRequest request, FtpWriter writer) throws IOException {
        try {
             String metName = "do" + request.getCommand();
             Method actionMet = getClass().getDeclaredMethod(metName, METHOD_INPUT_SIG);
             actionMet.invoke(this, new Object[] {request, writer});
         }
         catch(NoSuchMethodException ex) {
             writer.write(mFtpStatus.getResponse(502, request, mUser, null));
         }
         catch(InvocationTargetException ex) {
             writer.write(mFtpStatus.getResponse(500, request, mUser, null));
             Throwable th = ex.getTargetException();
             if (th instanceof java.io.IOException) {
                throw (IOException)th;
             }
             else {
                mConfig.getLogger().warn(th);
             }
         }
         catch(Exception ex) {
             writer.write(mFtpStatus.getResponse(500, request, mUser, null));
             if (ex instanceof java.io.IOException) {
                throw (IOException)ex;
             }
             else {
                mConfig.getLogger().warn(ex);
             }
         }
    }   
          
    /**
     * Check permission - default implementation - does nothing.
     */
    protected boolean hasPermission(FtpRequest request) {
        return true;
    }
    
    /**
     * User logout and stop this thread.
     */
    public void stop() {
        mbStopRequest = true;
        if (mDataConnection != null) {
            mDataConnection.dispose();
            mDataConnection = null;
        }
        if (mControlSocket != null) {
            try {
                mControlSocket.close();
            }
            catch(Exception ex) {
                mConfig.getLogger().warn(ex);
            }
            mControlSocket = null;
        }
        if (mUser.hasLoggedIn()) {
            mUser.logout();        
        }
        mObserver = null;
    }
     
    /**
     * Is the connection closed?
     */
    public boolean isClosed() {
        return mbStopRequest;
    }
    
    /**
     * Monitor the user request.
     */
    protected void spyRequest(final String str) {
        final SpyConnectionInterface spy = mSpy;
        if (spy != null) {
            Message msg = new Message() {
                public void execute() {
                    try {
                        spy.request(str + '\n');
                    }
                    catch(Exception ex) {
                        mSpy = null;
                        mConfig.getLogger().error(ex);
                    }
                }
            };
            mConfig.getMessageQueue().add(msg);
        }
    }
    
    /**
     * Get user object
     */
    public FtpUser getUser() {
        return mUser;
    }
     
    /**
     * Get connection spy object
     */
    public SpyConnectionInterface getSpyObject() {
        return mSpy;
    }
    
    /**
     * Set spy object
     */
    public void setSpyObject(SpyConnectionInterface spy) {
        mWriter.setSpyObject(spy);
        mSpy = spy;
    }
    
    /**
     * Get observer
     */
    public FtpConnectionObserver getObserver() {
        return mObserver;
    }

    /**
     * Set observer
     */
    public void setObserver(FtpConnectionObserver obsr) {
        mObserver = obsr;
    }

    /**
     * Notify observer.
     */
    public void notifyObserver() {
       mUser.hitUser();
       final FtpUser thisUser = mUser; 
       final FtpConnectionObserver obsr = mObserver;

       if (obsr != null) {
            Message msg = new Message() {
                public void execute() {
                    obsr.updateConnection(thisUser);
                }
            };
            mConfig.getMessageQueue().add(msg);
       }
    }
    
    /**
     * This method tracks data transfer.
     */
    public void dataTransferred(int sz) {
         notifyObserver();
    }
         
    /**
     * Get config object
     */
    public FtpConfig getConfig() {
        return mConfig;
    } 
    
    /**
     * Get status object
     */
    public FtpStatus getStatus() {
        return mFtpStatus;
    }  
         
}
 
